<template>
  <view class="s-popup" :class="c_class" :style="c_style">
    <view
      v-if="mask"
      class="s-popup__mask"
      :style="mask_Style"
      @touchmove.stop.prevent
      @click="maskClose && close()"
    ></view>
    <view class="s-popup__wrap" :class="wrap_class" :style="wrap_Style" @touchmove.stop.prevent>
      <s-icon
        v-if="closeable"
        :name="closeIcon"
        :size="closeSize"
        :color="closeColor"
        custom-class="s-popup__close-icon"
        :custom-style="closeStyle"
        @click="close"
      />
      <slot />
    </view>
  </view>
</template>

<script>
import componentMixin from '../../mixins/componentMixin';

// 记录弹框的zIndex
const zIndexMap = new Map();
const getMaxPopupZindex = () => {
  return Math.max(200, ...zIndexMap.values());
};

// position 默认效果
const animateMap = {
  center: 'zoom-in',
  left: 'slide-left',
  right: 'slide-right',
  top: 'slide-top',
  bottom: 'slide-bottom',
};

/**
 * s-popup 弹框
 * @description 基础弹框组件
 * @property {String} position = [center | left | right | top | bottom ] 显示位置
 * @property {Boolean} value v-model双向绑定显示关闭
 * @property {String} background 背景色
 * @property {Number|String} width wrap容器宽度
 * @property {Number|String} height wrap容器高度
 * @property {Number|String} maxWidth wrap容器最大宽度
 * @property {Number|String} maxHeight wrap容器最大高度
 * @property {Number|String} radius wrap容器圆角
 * @property {String|Object} wrapStyle wrap容器style样式
 * @property {Number|String} zIndex z-index,如没指定，则在显示时使用比较自增
 * @property {String|Boolean|Object} animate = [false|custom|fade|fade-top|slide-top|{enter:'fade',leave:'fade-top'}] 过渡效果，为s-ui styles下animate.scss中定义的效果
 * @property {Number|String|Object} animateDuration 显示关闭过渡效果时间 ms
 * @property {Boolean} mask 是否显示遮罩
 * @property {Number|String} maskOpacity 遮罩透明度 0.7
 * @property {String|Object} maskStyle 遮罩自定样式
 * @property {Boolean} maskClose 点击遮罩是否关闭
 * @property {Number|String} duration 自动关闭时间，为 0不会自动关闭
 * @property {Boolean} closeable 是否显示关闭图标
 * @property {Boolean} closeIcon 关闭图标名称或图片链接
 * @property {String} closeColor 关闭图标颜色
 * @property {String|Object} closeStyle 关闭图标样式
 * @property {Function} beforeOpen 显示时拦截钩子返回Boolean或Promise通过resolve(Boolean)拦截
 * @property {Function} beforeClose 关闭时拦截钩子返回Boolean或Promise通过resolve(Boolean)拦截
 * @event {Function} open 打开弹出层时触发
 * @event {Function} opened 打开弹出层且动画结束后触发
 * @event {Function} close 关闭弹出层时触发
 * @event {Function} closed 关闭弹出层且动画结束后触发
 * @example <s-popup v-model="visible"></s-popup>
 */
export default {
  name: 'SPopup',
  mixins: [componentMixin],
  props: {
    position: {
      type: String,
      default: 'center',
    },
    value: Boolean,
    background: String,
    width: [Number, String],
    height: [Number, String],
    radius: [Number, String],
    wrapStyle: [String, Object],
    zIndex: {
      type: [Number, String],
      default: 0,
    },
    animate: {
      type: [String, Boolean, Object],
      default: 'auto',
    },
    animateDuration: {
      type: [Number, String, Object],
      default: 300,
    },
    mask: {
      type: Boolean,
      default: true,
    },
    maskOpacity: {
      type: [Number, String],
      default: 0.7,
    },
    maskStyle: [String, Object],
    maskClose: {
      type: Boolean,
      default: true,
    },
    duration: {
      type: [Number, String],
      default: 0,
    },
    closeable: Boolean,
    closeIcon: {
      type: String,
      default: 'cross',
    },
    closeSize: [Number, String],
    closeColor: String,
    closeStyle: [String, Object],
    beforeOpen: Function,
    beforeClose: Function,
  },
  data() {
    return {
      zIndexValue: 1,
      animateStatus: '',
      isVisible: false,
    };
  },
  computed: {
    c_class() {
      return this.$mergeClass({
        [`s-popup--${this.position}`]: this.position && this.position !== 'custom',
        [`s-popup--${this.animateStatus}`]: this.animate && this.animateStatus,
      }, this.custom_class);
    },
    c_style() {
      return this.$mergeStyle({
        zIndex: this.zIndexValue > 0 ? this.zIndexValue : '',
        animationDuration: this.animateStatus ? this.animateDurationMap[this.animateStatus] + 'ms' : '',
        display: this.isVisible ? '' : 'none',
      }, this.custom_style);
    },
    animateName() {
      return this.animate && this.animate !== 'custom' ? this.animate : '';
    },
    animateNameMap() {
      if (this.animateName) {
        let name = this.animateName;
        if (name === 'auto') name = animateMap[this.position] || '';
        if (this.$getType(name) !== 'object') name = { enter: name, leave: name };
        return { enter: name.enter || '', leave: name.leave || '' };
      }
      return { enter: '', leave: '' };
    },
    animateDurationMap() {
      if (this.animate) {
        let duration = this.animateDuration;
        if (this.$getType(duration) !== 'object') duration = { enter: duration, leave: duration };
        return { enter: parseInt(duration.enter) || 0, leave: parseInt(duration.leave) || 0 };
      }
      return { enter: 0, leave: 0 };
    },
    wrap_class() {
      let animateNameClass = '';
      if (this.animateName) {
        const animateStatus = this.animateStatus;
        if (animateStatus && this.animateNameMap[animateStatus]) {
          animateNameClass = `s-animate-${this.animateNameMap[animateStatus]}-${animateStatus}`;
        }
      }
      return animateNameClass;
    },
    mask_Style() {
      return this.$mergeStyle({
        backgroundColor: 'rgba(0, 0, 0, ' + this.maskOpacity + ')',
      }, this.maskStyle);
    },
    wrap_Style() {
      return this.$mergeStyle({
        width: this.$addUnit(this.width),
        height: this.$addUnit(this.height),
        background: this.background,
        borderRadius: this.$addUnit(this.radius),
      }, this.wrapStyle);
    },
  },
  watch: {
    value() {
      this.updateVisible();
    },
  },
  created() {
    Object.assign(this, {
      visibleId: 0,
      visibleStatus: false,
      animateTimer: 0,
      autoCloseTimer: 0,
    });
  },
  mounted() {
    this.updateVisible();
  },
  beforeDestroy() {
    zIndexMap.delete(this.$sUid);
    clearTimeout(this.animateTimer);
    clearTimeout(this.autoCloseTimer);
  },
  methods: {
    async open() {
      if (!this.visibleStatus) {
        const visibleId = ++this.visibleId;
        const flag = this.beforeOpen ? (await this.$triggerPropsFn('beforeOpen')) && visibleId === this.visibleId : true;
        if (flag) {
          clearTimeout(this.animateTimer);
          clearTimeout(this.autoCloseTimer);
          let zIndex = this.zIndex;
          if (!(Number(zIndex) > 0)) {
            zIndex = getMaxPopupZindex() + 1;
            zIndexMap.set(this.$sUid, zIndex);
          }
          this.visibleStatus = true;
          this.zIndexValue = zIndex;
          this.isVisible = true;
          this.animateStatus = 'enter';
          this.$emit('input', true);
          this.$emit('open');
          this.animateTimer = setTimeout(() => {
            this.animateStatus = '';
            this.$emit('opened');
            // 自动关闭
            const duration = parseInt(this.duration);
            if (duration > 0) {
              this.autoCloseTimer = setTimeout(() => {
                this.visibleStatus && this.close();
              }, duration);
            }
          }, this.animateDurationMap.enter);
        } else {
          this.$emit('input', false);
        }
      }
    },
    async close() {
      if (this.visibleStatus) {
        const visibleId = ++this.visibleId;
        const flag = this.beforeClose ? (await this.$triggerPropsFn('beforeClose')) && visibleId === this.visibleId : true;
        if (flag) {
          clearTimeout(this.animateTimer);
          clearTimeout(this.autoCloseTimer);
          this.visibleStatus = false;
          this.animateStatus = 'leave';
          this.$emit('input', false);
          this.$emit('close');
          this.animateTimer = setTimeout(() => {
            this.isVisible = false;
            this.zIndexValue = 0;
            this.animateStatus = '';
            zIndexMap.delete(this.$sUid);
            this.$emit('closed');
          }, this.animateDurationMap.leave);
        } else {
          this.$emit('input', true);
        }
      }
    },
    updateVisible() {
      if (this._isMounted && this.visibleStatus !== this.value) {
        this[this.value ? 'open' : 'close']();
      }
    },
  },
};
</script>

<style lang="scss" src="./index.scss"></style>
